package cn.vehicle.controller;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.vehicle.domains.entity.CameraEntity;
import cn.vehicle.enums.StringTemplateEnum;
import cn.vehicle.exception.VehicleAccessException;
import cn.vehicle.service.CameraService;
import cn.vehicle.service.remote.TjRemoteService;
import cn.vehicle.utils.NvrUtil;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

/**
 * 视频流控制器
 * <p>
 * 提供视频流访问相关接口，包括：
 * <ul>
 *   <li>实时拉流（FFmpeg 转码输出 FLV 格式）</li>
 *   <li>历史录像回放</li>
 * </ul>
 * </p>
 *
 * <p>主要功能：</p>
 * <ul>
 *   <li>通过相机编号获取 RTSP/NVR 地址；</li>
 *   <li>校验 access_token，防止未授权访问；</li>
 *   <li>通过 FFmpeg 进程转封装输出流至 HTTP 响应。</li>
 * </ul>
 */
@RestController
@RequestMapping("/api/stream")
@Slf4j
public class StreamController {

    @Resource
    private CameraService cameraService;

    @Resource
    private NvrUtil nvrUtil;

    @Resource
    private TjRemoteService tjRemoteService;

    /**
     * 启动视频流推送接口
     * <p>
     * 根据相机编号拉取 RTSP 流并使用 FFmpeg 推送为 HTTP-FLV 格式输出。
     * 支持根据时间区间拉取录像回放。
     * </p>
     *
     * @param response HTTP 响应对象，用于输出视频流
     * @param cameraCode 相机编码，用于定位具体摄像头
     * @param accessToken 访问令牌（可选），用于权限验证
     * @param startTime 回放起始时间（可选）
     * @param endTime 回放结束时间（可选）
     */
    @GetMapping("/startStream/{cameraCode}")
    public void startStream(HttpServletResponse response, @PathVariable String cameraCode,
                            @RequestParam(value = "access_token", required = false) String accessToken,
                            @RequestParam(value = "starttime", required = false) String startTime,
                            @RequestParam(value = "endtime", required = false)String endTime ) {
        Process ffmpegProcess;


        try {
            // 校验 accessToken，防止非法访问；若不匹配则直接返回
            if (StrUtil.isNotEmpty(accessToken)
                && !accessToken.equals(tjRemoteService.getAccessToken())) {
                return;
            }
            // 通过相机编码获取相机信息
            CameraEntity cameraEntity = cameraService.getCameraByCameraCode(cameraCode);

            // 若相机不存在，抛出业务异常
            if (cameraEntity == null) {
                throw VehicleAccessException.DATE_NOT_EXIST.newInstance("相机不存在");
            }
            // 根据相机的通道号、NVR 地址和时间参数拼接 RTSP/NVR 流地址
            String nvrUrl = nvrUtil.getNvrUrl(cameraEntity.getChannelCode(), cameraEntity.getNvrAddressUrl(), startTime, endTime);

            // 生成 FFmpeg 执行命令（实时流或回放流）
            String command = getFFmpegCommand(checkStartStreamParam(startTime, endTime), nvrUrl);

            log.info("command:{}", command);
            // 设置响应类型为视频流（FLV 格式），支持跨域访问
            response.setContentType("video/x-flv");
            response.setHeader("Access-Control-Allow-Origin", "*");
            // 创建 FFmpeg 进程构建器并配置命令参数
            ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
            processBuilder.redirectErrorStream(true);


            ffmpegProcess = processBuilder.start();

            // 启动 FFmpeg 进程并将其标准输出流写入 HTTP 响应
            try (InputStream ffmpegOutput = ffmpegProcess.getInputStream();
                 OutputStream responseOutput = response.getOutputStream()) {

                byte[] buffer = new byte[4096];
                int bytesRead;
                log.info("[startStream] start read");
                // 循环读取 FFmpeg 输出的字节流并实时写入客户端响应
                while ((bytesRead = ffmpegOutput.read(buffer)) != -1) {
                    responseOutput.write(buffer, 0, bytesRead);
                    responseOutput.flush();
                }

            } catch (AsyncRequestNotUsableException e) {
                log.info("[startStream] ffmpeg AsyncRequestNotUsableException.", e);
            } catch (Exception e) {
                log.error("[startStream] ffmpeg error.", e);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } finally {
                // 等待 FFmpeg 执行结束并销毁进程，防止资源泄漏
                try {
                    ffmpegProcess.waitFor();
                } catch (InterruptedException e) {
                    log.error("[startStream InterruptedException] error");
                } finally {
                    ffmpegProcess.destroy();
                }
            }
            log.info("[startStream] read end");
        } catch (Exception e) {
            log.error("[startStream] error", e);
        }
    }


    /**
     * 生成 FFmpeg 命令
     * <p>
     * 根据是否存在时间区间参数动态拼接 FFmpeg 命令模板。
     * </p>
     *
     * @param between 时间差（秒），0 表示实时流
     * @param rtspUrl RTSP 流地址
     * @return 完整的 FFmpeg 命令字符串
     */
    private String getFFmpegCommand(int between, String rtspUrl) {
        // 构造模板变量，将时间差与 RTSP 地址注入命令模板
        final JSONObject vars = new JSONObject()
                .fluentPut("between", between)
                .fluentPut("rtspUrl", rtspUrl);
        final String template = between == 0 ?
                "ffmpeg -rtsp_transport tcp -i \"${rtspUrl}\" -c:v copy -an -f flv -loglevel quiet pipe:1"
                : "ffmpeg -rtsp_transport tcp -i \"${rtspUrl}\" -c:v copy -an -t ${between} -f flv -loglevel quiet pipe:1";
        final String command = StringTemplateEnum.bracket.replace(template, vars);
        log.info("[getFFmpegCommand] template:{}, command:{}, vars:{}", template, command, vars);
        return command;
    }


    /**
     * 校验视频流请求参数
     * <p>
     * 校验起止时间合法性并计算时间差（用于回放时长）。
     * 若仅传一个参数或起止时间相同，则抛出参数异常。
     * </p>
     *
     * @param startTime 起始时间
     * @param endTime 结束时间
     * @return 起止时间之间的秒数；若为实时流则返回 0
     */
    private int checkStartStreamParam(String startTime, String endTime) {

        // 若仅传入 startTime 或 endTime 之一，则为非法参数
        if ((StrUtil.isEmpty(startTime) && StrUtil.isNotEmpty(endTime))
                || (StrUtil.isNotEmpty(startTime) && StrUtil.isEmpty(endTime))) {
            log.error("[StreamController checkStartStreamParam]  time error startTime:{} endTime:{}", startTime, endTime);
            throw VehicleAccessException.PARAMS_ERROR;
        }

        if (StrUtil.isNotEmpty(startTime)) {
            // 起止时间相同，不合法，直接报错
            if (startTime.equals(endTime)) {
                log.error("[StreamController checkStartStreamParam] startTime == endTime startTime:{} endTime:{}", startTime, endTime);
                throw VehicleAccessException.PARAMS_ERROR;
            }

            // 将字符串时间转为 Date 对象
            Date startDate = DateUtil.parse(startTime.replace("t", "").replace("z", ""),
                    DatePattern.PURE_DATETIME_PATTERN);

            Date endDate = DateUtil.parse(endTime.replace("t", "").replace("z", ""),
                    DatePattern.PURE_DATETIME_PATTERN);

            // 计算时间间隔（单位：秒），用于生成 FFmpeg 回放时长参数
            int between = (int) DateUtil.between(startDate, endDate, DateUnit.SECOND);
            log.info("[StreamController checkStartStreamParam] startTime endTime between :{}", between);
            return between;
        }
        return 0;
    }
}
